//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#include "NjNinjaSync.h"
#include "NjRecursiveMutex.h"

class NjRecursiveMutex::Static
{
public:
	static NjUint GetUniqueThreadId();

private:
	static NjAtomic<NjUint> ms_kThreadIdCounter;
	static NJ_THREAD_DATA NjUint ms_uThreadId;

	NJ_DISABLE_INSTANCE(Static);
};
//----------------------------------------------------------------------------
NjBool NjRecursiveMutex::TryLock(NjUint uTimeMs)
{
	NjUint uCurrentThreadId(Static::GetUniqueThreadId());

	if ((m_uRecursionData&THREAD_ID_MASK)==uCurrentThreadId)
	{
#if NJ_ASSERT_ENABLED
		NjUint uRecursionCounter=
#endif // NJ_ASSERT_ENABLED
		
		m_uRecursionData+=RECURSION_COUNTER;

		NJ_ASSERT((uRecursionCounter&RECURSION_MASK)!=0);

		return NJ_TRUE;
	}

	if (!m_kMutex.TryLock(uTimeMs))
		return NJ_FALSE;

	NJ_ASSERT(m_uRecursionData==INVALID_THREAD_ID);

	m_uRecursionData=uCurrentThreadId+RECURSION_COUNTER;
	return NJ_TRUE;
}
//----------------------------------------------------------------------------
void NjRecursiveMutex::Unlock()
{
	NJ_ASSERT((m_uRecursionData&THREAD_ID_MASK)==Static::GetUniqueThreadId());

	NjUint uRecursionCounter(m_uRecursionData-=RECURSION_COUNTER);
	uRecursionCounter&=RECURSION_MASK;

	if (uRecursionCounter!=0)
	{
		NJ_ASSERT(uRecursionCounter!=RECURSION_MASK);
		return;
	}

	m_uRecursionData=INVALID_THREAD_ID;
	m_kMutex.Unlock();
}
//----------------------------------------------------------------------------
NjUint NjRecursiveMutex::Static::GetUniqueThreadId()
{
	NjUint uThreadId(ms_uThreadId);
	if (uThreadId!=INVALID_THREAD_ID)
		return uThreadId;

	uThreadId=ms_kThreadIdCounter.Get();
	for(;;)
	{
		NjUint uNewThreadId;
		if (uThreadId==THREAD_ID_MASK)
			uNewThreadId=THREAD_ID_COUNTER;
		else
			uNewThreadId=uThreadId+THREAD_ID_COUNTER;

		NjUint const uOldThreadId(ms_kThreadIdCounter.PostCompareExchange(uThreadId, uNewThreadId));
		if (uOldThreadId==uThreadId)
		{
			ms_uThreadId=uNewThreadId;
			return uNewThreadId;
		}

		uThreadId=uOldThreadId;
	}
}
//----------------------------------------------------------------------------
NjAtomic<NjUint> NjRecursiveMutex::Static::ms_kThreadIdCounter(0);
NJ_THREAD_DATA NjUint NjRecursiveMutex::Static::ms_uThreadId(0);
//----------------------------------------------------------------------------
